<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<style type="text/css">
		body {
			margin: 0;
			padding: 0;
			height: 100%;
            overflow: hidden;
		}
		.container {
			display: grid;
			grid-template: "left middle" 1fr /
							200px 1fr;
			height: 100vh;
		}

		.container .left {
			display: grid;
			grid-template:
				"welcome" auto
				"rooms" 1fr /
				 1fr;
			min-height: 0;
		}

		.container .middle {
			display: grid;
			grid-template:
				"header" auto
				"timeline" 1fr
				"composer" auto /
				 1fr;
			min-height: 0;
            position: relative;
		}

		.left { grid-area: left;}
		.left p {
			grid-area welcome;
			display: flex;
		}
		.left ul {
			grid-area: rooms;
			min-height: 0;
			overflow-y: auto;
		}

		.middle { grid-area: middle;}
		.middle .header { grid-area: header;}
		.middle .timeline {
			grid-area: timeline;
			min-height: 0;
			overflow-y: auto;
		}
		.middle .composer {
			grid-area: composer;
		}

		.header {
			display: flex;
		}

		.header h2 {
			flex: 1;
		}

        .composer {
            display: flex;
        }

        .composer input {
            display: block;
            flex: 1;
        }

		.menu {
			position: absolute;
			border-radius: 8px;
            box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
            padding: 16px;
            background-color: white;
			z-index: 1;
            list-style: none;
            margin: 0;
		}
	</style>
</head>
<body>
	<div class="container">
		<div class="left">
			<p>Welcome!<button>⋮</button></p>
			<ul>
				<li>Room xyz <button>⋮</button></li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz <button>⋮</button></li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz</li>
				<li>Room xyz <button>⋮</button></li>
			</ul>
		</div>
		<div class="middle">
			<div class="header">
				<h2>Room xyz</h2>
				<button>⋮</button>
			</div>
			<ul class="timeline">
                <li>Message abc</li>
				<li>Message abc <button>⋮</button></li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc <button>⋮</button></li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc</li>
				<li>Message abc <button>⋮</button></li>
			</ul>
			<div class="composer">
				<input type="text" name="">
				<button>⋮</button>
			</div>
		</div>
	</div>
	<script type="text/javascript">
        let menu;

        function createMenu(options) {
            const menu = document.createElement("ul");
            menu.className = "menu";
            for (const o of options) {
                const li = document.createElement("li");
                li.innerText = o;
                menu.appendChild(li);
            }
            return menu;
        }


        function showMenu(evt) {
            if (menu) {
                menu = menu.close();
            } else if (evt.target.tagName.toLowerCase() === "button") {
                menu = showPopup(evt.target, createMenu(["Send file", "Save contact", "Send picture", "Foo the bar"]), {
                    horizontal: {
                        relativeTo: "end",
                        align: "start",
                        after: 0,
                    },
                    vertical: {
                        relativeTo: "end",
                        align: "end",
                        after: 10,
                    }
                });
            }
        }

        function showMenuInScroller(evt) {
            if (!menu && evt.target.tagName.toLowerCase() === "button") {
                evt.stopPropagation();
                menu = showPopup(evt.target, createMenu(["Show reactions", "Share"]), {
                    horizontal: {
                        relativeTo: "start",
                        align: "end",
                        after: 10,
                    },
                    vertical: {
                        relativeTo: "start",
                        align: "center",
                    }
                });
            }
        }

		document.body.addEventListener("click", showMenu, false);
        document.querySelector(".middle ul").addEventListener("click", showMenuInScroller, false);
        document.querySelector(".left ul").addEventListener("click", showMenuInScroller, false);

		function showPopup(target, popup, arrangement) {
            targetAxes = elementToAxes(target);
            if (!arrangement) {
                arrangement = getAutoArrangement(targetAxes);
            }
			
			target.offsetParent.appendChild(popup);

            const popupAxes = elementToAxes(popup);
            const scrollerAxes = elementToAxes(findScrollParent(target));
            const offsetParentAxes = elementToAxes(target.offsetParent);

            function reposition() {
                if (scrollerAxes && !isVisibleInScrollParent(targetAxes.vertical, scrollerAxes.vertical)) {
                    popupObj.close();
                }
                applyArrangement(
                    popupAxes.vertical,
                    targetAxes.vertical,
                    offsetParentAxes.vertical,
                    scrollerAxes?.vertical,
                    arrangement.vertical
                );
                applyArrangement(
                    popupAxes.horizontal,
                    targetAxes.horizontal,
                    offsetParentAxes.horizontal,
                    scrollerAxes?.horizontal,
                    arrangement.horizontal
                );
            }
            reposition();
            
            document.body.addEventListener("scroll", reposition, true);

            const popupObj = {
                close() {
                    document.body.removeEventListener("scroll", reposition, true);
                    popup.remove();
                }
            };

            return popupObj;
		}

        function elementToAxes(element) {
            if (element) {
                return {
                    horizontal: new HorizontalAxis(element),
                    vertical: new VerticalAxis(element),
                    element
                };
            }
        }

        function findScrollParent(el) {
            let parent = el;
            do {
                parent = parent.parentElement;
                if (parent.scrollHeight > parent.clientHeight) {
                    return parent;
                }
            } while (parent !== el.offsetParent);
        }

        function isVisibleInScrollParent(targetAxis, scrollerAxis) {
            // clipped at start?
            if ((targetAxis.offsetStart + targetAxis.clientSize) < (
                scrollerAxis.offsetStart + 
                scrollerAxis.scrollOffset
            )) {
                return false;
            }
            // clipped at end?
            if (targetAxis.offsetStart > (
                scrollerAxis.offsetStart + 
                scrollerAxis.clientSize + 
                scrollerAxis.scrollOffset
            )) {
                return false;
            }
            return true;
        }

        function applyArrangement(elAxis, targetAxis, offsetParentAxis, scrollerAxis, {relativeTo, align, before, after}) {
            if (relativeTo === "end") {
                let end = offsetParentAxis.clientSize - targetAxis.offsetStart;
                if (align === "end") {
                    end -= elAxis.offsetSize;
                } else if (align === "center") {
                    end -= ((elAxis.offsetSize / 2) - (targetAxis.offsetSize / 2));
                }
                if (typeof before === "number") {
                    end += before;
                } else if (typeof after === "number") {
                    end -= (targetAxis.offsetSize + after);
                }
                elAxis.end = end;
            } else if (relativeTo === "start") {
                let scrollOffset = scrollerAxis?.scrollOffset || 0;
                let start = targetAxis.offsetStart - scrollOffset;
                if (align === "start") {
                    start -= elAxis.offsetSize;
                } else if (align === "center") {
                    start -= ((elAxis.offsetSize / 2) - (targetAxis.offsetSize / 2));
                }
                if (typeof before === "number") {
                    start -= before;
                } else if (typeof after === "number") {
                    start += (targetAxis.offsetSize + after);
                }
                elAxis.start = start;
            } else {
                throw new Error("unknown relativeTo: " + relativeTo);
            }
        }

        class HorizontalAxis {
            constructor(el) {
                this.element = el;
            }
            get scrollOffset() {return this.element.scrollLeft;}
            get clientSize() {return this.element.clientWidth;}
            get offsetSize() {return this.element.offsetWidth;}
            get offsetStart() {return this.element.offsetLeft;}
            set start(value) {this.element.style.left = `${value}px`;} 
            set end(value) {this.element.style.right = `${value}px`;}
        }
        class VerticalAxis {
            constructor(el) {
                this.element = el;
            }
            get scrollOffset() {return this.element.scrollTop;}
            get clientSize() {return this.element.clientHeight;}
            get offsetSize() {return this.element.offsetHeight;}
            get offsetStart() {return this.element.offsetTop;}
            set start(value) {this.element.style.top = `${value}px`;} 
            set end(value) {this.element.style.bottom = `${value}px`;}
        }
	</script>
</body>
</html>
